#include <iostream>

extern "C" {
#include "libavutil/frame.h"
#include "libavutil/mem.h"
#include "libavcodec/avcodec.h"
}

#define AUDIO_INBUF_SIZE    (20480)
#define AUDIO_REFILL_THRESH (4096)

static char errBuf[128] = {0};
static char* avGetErr(int errNum) {
    av_strerror(errNum, errBuf, 128);
    return errBuf;
}

static void printSampleFormat(const AVFrame *frame) {
    std::cout << "ar-samplerate: " << frame->sample_rate << "Hz" << std::endl;
    std::cout << "ac-channel: " << frame->channels << std::endl;
    std::cout << "f-format: " << frame->format << std::endl;
    //格式需要注意，实际存储到本地文件时已经改成交错模式
}

static void decode(AVCodecContext *decCtx, AVPacket *packet, AVFrame *frame, FILE *outFile) {
    int i, channelIndex, ret, dataSize;
    //send the packet with the compressed data to the decoder
    ret = avcodec_send_packet(decCtx, packet);
    if (ret == AVERROR(EAGAIN))
        std::cout << "Receive_frame and send_packet both returned EAGAIN, which is an API violation." << std::endl;
    else if (ret < 0) {
        std::cout << "Errror submitting the packet to the decoder, err: " << avGetErr(ret) << " packetSize: " << packet->size << std::endl;
        return;
    }

    //read all the output frames (infile general there may be any number of them)
    while (ret >= 0) {
        //对于 frame，avcodec_receive_frame 内部每次都先调用
        ret = avcodec_receive_frame(decCtx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            std::cout << "Error during decoding." << std::endl;
            exit(1);
        }

        dataSize =  av_get_bytes_per_sample(decCtx->sample_fmt);

        if (dataSize < 0) {
            //This should not occur, checking just for paranoia.
            std::cout << "Failed to calculate data size." << std::endl;
            exit(1);
        }

        static int s_printFormat = 0;
        if (s_printFormat == 0) {
            s_printFormat = 1;
            printSampleFormat(frame);
        }
        /*
         * P 表示 Plannar(平面),其数据格式排列方式为:
         * LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...（每个LLLLLLRRRRRR为一个音频帧）
         * 而不带 P 的数据格式(即交错排列)排列方式为:
         * LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...（每个LR为一个音频样本）
         * 播放范例: ffplay -ar 48000 -ac 2 -f f32le believe.pcm
         */
        for (i = 0; i < frame->nb_samples; i++) {
            for (channelIndex = 0; channelIndex < decCtx->channels; channelIndex++) //交错的方式写入，大部分 float 的格式输出
                fwrite(frame->data[channelIndex] + dataSize * i, 1, dataSize, outFile);
        }
    }
}

//播放范例: ffplay -ar 48000 -ac 2 -f f32le believe.pcm
int main(int argc, char *argv[])
{
    std::string outputFileName, inputFileName;
    const AVCodec *codec = NULL;
    AVCodecContext *codecCtx = NULL;
    AVCodecParserContext *parser = NULL;
    int len = 0, ret = 0;
    FILE *inFile = NULL, *outFile = NULL;
    uint8_t inbuf[AUDIO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data = NULL;
    size_t dataSize = 0;
    AVPacket *packet = NULL;
    AVFrame *decodeFrame = NULL;

    if (argc <= 2) {
        std::cout << "Usage: " << argv[0] << "<inputFile> <outputFile>" << std::endl;
        exit(0);
    }

    inputFileName = std::string(argv[1]);
    outputFileName = std::string(argv[2]);

    packet = av_packet_alloc();
    enum AVCodecID audioCodecId = AV_CODEC_ID_AAC;
    if (strstr(inputFileName.c_str(), "aac") != NULL)
        audioCodecId = AV_CODEC_ID_AAC;
    else if (strstr(inputFileName.c_str(), "mp3") != NULL)
        audioCodecId = AV_CODEC_ID_MP3;
    else
        std::cout << "Default codec id: " << audioCodecId << std::endl;

    //查找解码器
    codec = avcodec_find_decoder(audioCodecId);
    if (codec == NULL) {
        std::cout << "Codec not found." << std::endl;
        exit(1);
    }

    //获取裸流的解析器 AVCodecParserContext(数据) + AVcodecParser(方法)
    parser = av_parser_init(codec->id);
    if (parser == NULL) {
        std::cout << "Parser not found." << std::endl;
        exit(1);
    }

    //分配 codec 上下文
    codecCtx = avcodec_alloc_context3(codec);
    if (codecCtx == NULL) {
        std::cout << "Could not allocate audio codec context." << std::endl;
        exit(1);
    }

    //将解码器和解码器上下文进行关联
    if (avcodec_open2(codecCtx, codec, NULL) < 0) {
        std::cout << "Could not open codec." << std::endl;
        exit(1);
    }

    //打开输入文件
    inFile = fopen(inputFileName.c_str(), "rb");
    if (inFile == NULL) {
        std::cout << "Could not open " << inFile << std::endl;
        exit(1);
    }

    //打开输出文件
    outFile = fopen(outputFileName.c_str(), "wb");
    if (outFile == NULL) {
        av_free(codecCtx);
        exit(1);
    }

    //读取文件进行解码
    data = inbuf;
    dataSize = fread(inbuf, 1, AUDIO_INBUF_SIZE, inFile);

    while (dataSize > 0) {
        if (decodeFrame == NULL) {
            decodeFrame = av_frame_alloc();
            if (decodeFrame == NULL) {
                std::cout << "Could not allocate audio frame." << std::endl;
                exit(1);
            }
        }

        ret = av_parser_parse2(parser, codecCtx, &packet->data, &packet->size,
                               data, dataSize,
                               AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
        if (ret < 0) {
            std::cout << "Error while parsing." << std::endl;
            exit(1);
        }

        data     += ret; //跳过已经解析的数据
        dataSize -= ret; //对应的缓存大小也做相应减少

        if (packet->size > 0)
            decode(codecCtx, packet, decodeFrame, outFile);

        if (dataSize < AUDIO_REFILL_THRESH) {//如果数据少则再次读取
            memmove(inbuf, data, dataSize); //把之前剩的数据拷贝到 buffer 的起始位置
            data = inbuf;
            //读取数据长度:AUDIO_INBUF_SIZE - dataSize
            len = fread(data + dataSize, 1, AUDIO_INBUF_SIZE - dataSize, inFile);
            if (len > 0)
                dataSize += len;
        }
    }

    //冲刷解码器
    packet->data = NULL;// 让其进入 drain mode
    packet->size = 0;
    decode(codecCtx, packet, decodeFrame, outFile);

    fclose(outFile);
    fclose(inFile);

    avcodec_free_context(&codecCtx);
    av_parser_close(parser);
    av_frame_free(&decodeFrame);
    av_packet_free(&packet);

    std::cout << "Main finish, please enter Enter and exit." << std::endl;

    return 0;
}
